package checkers;

import javax.imageio.ImageIO;
import javax.swing.*;

import checkers.MenuManager.frameListener;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

/**
 * Class representing the Game User Interface.
 * 
 */
public class GameUI extends JPanel implements ActionListener,
		MouseMotionListener, MouseListener {

	private static final long serialVersionUID = 1L;
	private static final String IMAGE_DIR = "/resources/";
	private static final String PIECE_IMAGE_DIR = "/resources/pieces/";
	private static final String DEFAULT_PIECE = "regular/";
	private static final String PIRATE_PIECE = "pirate/";
	private static final String DEFAULT_BOARD = "board2.png";
	private static final String PIRATE_BOARD = "board3.png";
	private static final int X_OFFSET = -2;
	private static final int Y_OFFSET = 6;
	private static final int HEIGHT = 540;
	private static final int WIDTH = 960;
	private static final int SIDES = 4;
	private static final int CLOCK_OFFSET = 20;
	private final double BASE_SCALE_X = .50;
	private final double BASE_SCALE_Y = .50;
	private final int LINE_WIDTH = 3;
	private final int PAUSE = 500;
	private final URL internalImagePath;
	private final URL internalPieceImagePath;
	private final int playerIndicatorXPos = 350;
	private final int playerIndicatorYPos = 90;
	private final int playerWinBannerXPos = 300;
	private final int playerWinBannerYPos = 90;

	Game game;
	BufferedImage board;
	JLabel surface;
	BufferedImage boardImg;
	BufferedImage bGImg;
	BufferedImage[][] sPieceImg;
	BufferedImage[][] bPieceImg;
	BufferedImage[][] sKPieceImg;
	BufferedImage[][] bKPieceImg;
	BufferedImage titleScreen;
	BufferedImage turnP1;
	BufferedImage turnP2;
	BufferedImage p1win;
	BufferedImage p2win;
	Font textFont;

	boolean mouseLeftPressed;
	boolean mouseRightPressed;
	boolean ai; // true if human vs. AI, false if human vs. human
	boolean gameInProgress;
	boolean justLaunched;

	String pieceType;
	String boardType;
	static MenuManager menu;
	static SaveAPI gameSaver;
	long startTime;
	static Clock p1Timer;
	static Clock p2Timer;

	/**
	 * Default constructor for the GameUI. Sets up all required paths and
	 * variables, and displays the GUI.
	 */
	public GameUI() {

		ai = true;
		internalImagePath = getClass().getResource(IMAGE_DIR);
		internalPieceImagePath = getClass().getResource(PIECE_IMAGE_DIR);
		boardType = DEFAULT_BOARD;
		pieceType = DEFAULT_PIECE;

		setLayout(null);
		initializeImages();

		// these values don't matter; this game is hidden behind the title
		// screen
		game = new Game(ai, 100);
		gameInProgress = false;

		justLaunched = true;

		setPreferredSize(new Dimension(WIDTH, HEIGHT));
		addMouseListener(this);
		mouseLeftPressed = false;
		mouseRightPressed = false;

	}

	/**
	 * Initializes the BufferedImage references with the proper images contained
	 * in the IMAGE_DIR directory.
	 */
	private void initializeImages() {

		sPieceImg = new BufferedImage[Game.BOARD_LENGTH][Game.BOARD_LENGTH];
		bPieceImg = new BufferedImage[Game.BOARD_LENGTH][Game.BOARD_LENGTH];
		sKPieceImg = new BufferedImage[Game.BOARD_LENGTH][Game.BOARD_LENGTH];
		bKPieceImg = new BufferedImage[Game.BOARD_LENGTH][Game.BOARD_LENGTH];

		try {
			titleScreen = ImageIO.read(new URL(internalImagePath
					+ "flyingmooselogo.png"));
			boardImg = ImageIO.read(new URL(internalImagePath + boardType));
			bGImg = ImageIO.read(new URL(internalImagePath + "bgblk.png"));
			turnP1 = ImageIO.read(new URL(internalImagePath + "turnP1.png"));
			turnP2 = ImageIO.read(new URL(internalImagePath + "turnP2.png"));
			p1win = ImageIO.read(new URL(internalImagePath + "p1win.png"));
			p2win = ImageIO.read(new URL(internalImagePath + "p2win.png"));

			for (int i = 0; i < Game.BOARD_LENGTH; i++) {
				for (int j = (i + 1) % 2; j < Game.BOARD_LENGTH; j += 2) {
					System.out.println("looking for" + internalPieceImagePath
							+ "p1" + (i) + (j) + ".png");
					sPieceImg[i][j] = ImageIO.read(new URL(
							internalPieceImagePath + pieceType + "p1" + (i)
									+ (j) + ".png"));
					System.out.println("looking for" + internalPieceImagePath
							+ "p2" + (i) + (j) + ".png");
					bPieceImg[i][j] = ImageIO.read(new URL(
							internalPieceImagePath + pieceType + "p2" + (i)
									+ (j) + ".png"));
					System.out.println("looking for" + internalPieceImagePath
							+ "p1k" + (i) + (j) + ".png");
					sKPieceImg[i][j] = ImageIO.read(new URL(
							internalPieceImagePath + pieceType + "p1k" + (i)
									+ (j) + ".png"));
					System.out.println("looking for" + internalPieceImagePath
							+ "p2k" + (i) + (j) + ".png");
					bKPieceImg[i][j] = ImageIO.read(new URL(
							internalPieceImagePath + pieceType + "p2k" + (i)
									+ (j) + ".png"));
				}
			}

		} catch (IOException e) {
			System.out.println("an Image file was not found. Exiting program");
			System.exit(1);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
	 * 
	 * Overloaded paint to do what I say. It had a rather boring life before I
	 * got to it anyway.
	 */
	public void paintComponent(Graphics g) {

		boolean[][][] currentBoard = game.getBoard();

		Position position;
		Color previousColor;
		BufferedImage resizedImage;
		BufferedImage offScreen = new BufferedImage(WIDTH, HEIGHT,
				BufferedImage.TYPE_INT_RGB);

		Graphics gBuff = offScreen.createGraphics();

		if (gameInProgress) {
			/*
			 * this is needed to resize the line width on the highlighting
			 */
			Graphics2D g2 = (Graphics2D) gBuff;
			g2.setStroke(new BasicStroke(LINE_WIDTH));

			/*
			 * draws the background
			 */
			gBuff.drawImage(bGImg, 0, 0, null);

			/*
			 * draws the board
			 */
			gBuff.drawImage(boardImg, 0, 0, null);

			/*
			 * draw if player has won
			 */
			if (game.winner) {
				gameInProgress = false;
				if (game.player) {
					// gBuff.drawImage(p1win,WIDTH/2 - p1win.getWidth()/2 ,
					// p1win.getHeight()/2 , null);
					textFont = new Font("Serif", Font.PLAIN, 50);
					gBuff.setFont(textFont);
					gBuff.drawString("PLAYER 1 WINS!", playerWinBannerXPos,
							playerWinBannerYPos);

					game.elapsedTime += System.currentTimeMillis() - startTime;
					gameSaver.addStatistic("win", game.elapsedTime, !ai);
					p1Timer.stop();
					p2Timer.stop();
					menu.enableButtons(true, false, true, false);
					gameInProgress = false;
				} else {
					// gBuff.drawImage(p2win,WIDTH/2 - p2win.getWidth()/2 ,
					// p2win.getHeight()/2 , null);
					textFont = new Font("Serif", Font.PLAIN, 50);
					gBuff.setFont(textFont);
					gBuff.drawString("PLAYER 2 WINS!", playerWinBannerXPos,
							playerWinBannerYPos);

					game.elapsedTime += System.currentTimeMillis() - startTime;
					gameSaver.addStatistic("loss", game.elapsedTime, !ai);
					p1Timer.stop();
					p2Timer.stop();
					menu.enableButtons(true, false, true, false);
					gameInProgress = false;
				}
			}

			/*
			 * draws player turn indicator
			 */
			if (!game.player && !game.winner) {
				// gBuff.drawImage(turnP1, (int)(WIDTH/2-turnP1.getWidth()/2),
				// (int)(HEIGHT-1.5*turnP1.getHeight()), null);
				textFont = new Font("Serif", Font.PLAIN, 50);
				gBuff.setFont(textFont);
				gBuff.drawString("Turn: Player 1", playerIndicatorXPos,
						playerIndicatorYPos);
			} else if (game.player && !game.winner) {
				// gBuff.drawImage(turnP2, (int)(WIDTH/2-turnP2.getWidth()/2),
				// (int)(HEIGHT-1.5*turnP2.getHeight()), null);
				textFont = new Font("Serif", Font.PLAIN, 50);
				gBuff.setFont(textFont);
				gBuff.drawString("Turn: Player 2", playerIndicatorXPos,
						playerIndicatorYPos);
			}

			/*
			 * draws the highlighting for selected spaces
			 */
			for (int i = 0; i < Game.BOARD_LENGTH; i++) {
				for (int j = 0; j < Game.BOARD_LENGTH; j++) {
					position = findPositionFromIndex(i, j);
					if (currentBoard[i][j] != null) {

						if (currentBoard[i][j][Game.SELECTED_INDEX]) {

							if (currentBoard[i][j][Game.OCCUPIED_INDEX]) {

								if (currentBoard[i][j][Game.PLAYER_INDEX] == game
										.currentPlayer()) {
									previousColor = gBuff.getColor();
									gBuff.setColor(Color.GREEN);
								} else {
									previousColor = gBuff.getColor();
									gBuff.setColor(Color.ORANGE);
								}
							} else {
								previousColor = gBuff.getColor();
								gBuff.setColor(Color.CYAN);
							}
							gBuff.drawLine(position.topLeft.x,
									position.topLeft.y, position.topRight.x,
									position.topRight.y);
							gBuff.drawLine(position.topRight.x,
									position.topRight.y,
									position.bottomRight.x,
									position.bottomRight.y);
							gBuff.drawLine(position.bottomRight.x,
									position.bottomRight.y,
									position.bottomLeft.x,
									position.bottomLeft.y);
							gBuff.drawLine(position.bottomLeft.x,
									position.bottomLeft.y, position.topLeft.x,
									position.topLeft.y);
							gBuff.setColor(previousColor);
						}
					}
				}
			}

			/*
			 * draws the pieces
			 */
			for (int i = 0; i < Game.BOARD_LENGTH; i++) {
				for (int j = 0; j < Game.BOARD_LENGTH; j++) {
					position = findPositionFromIndex(i, j);
					if (currentBoard[i][j] != null) {

						if (currentBoard[i][j][Game.OCCUPIED_INDEX]) {

							if (currentBoard[i][j][Game.PLAYER_INDEX]) {

								if (currentBoard[i][j][Game.KING_INDEX]) {
									resizedImage = resizeImage(bKPieceImg[i][j]);
									gBuff.drawImage(resizedImage,
											position.midPoint().x
													- resizedImage.getWidth()
													/ 2 - X_OFFSET,
											position.midPoint().y
													- resizedImage.getHeight()
													/ 2 - Y_OFFSET, null);
								} else {
									resizedImage = resizeImage(bPieceImg[i][j]);
									gBuff.drawImage(resizedImage,
											position.midPoint().x
													- resizedImage.getWidth()
													/ 2 - X_OFFSET,
											position.midPoint().y
													- resizedImage.getHeight()
													/ 2 - Y_OFFSET, null);
								}
							} else {

								if (currentBoard[i][j][Game.KING_INDEX]) {
									resizedImage = resizeImage(sKPieceImg[i][j]);
									gBuff.drawImage(resizedImage,
											position.midPoint().x
													- resizedImage.getWidth()
													/ 2 - X_OFFSET,
											position.midPoint().y
													- resizedImage.getHeight()
													/ 2 - Y_OFFSET, null);
								} else {
									resizedImage = resizeImage(sPieceImg[i][j]);
									gBuff.drawImage(resizedImage,
											position.midPoint().x
													- resizedImage.getWidth()
													/ 2 - X_OFFSET,
											position.midPoint().y
													- resizedImage.getHeight()
													/ 2 - Y_OFFSET, null);
								}
							}
						}
					}
				}
			}
		} else {
			gBuff.setColor(Color.BLACK);
			gBuff.drawImage(titleScreen, 0, 0, null);
		}
		g.drawImage(offScreen, 0, 0, null);
	}

	/**
	 * dummy update method that calls the paint method.
	 * used to force repaint quicker than calling repaint()
	 */
	public void update(Graphics g) {
		paint(g);
	}

	/**
	 * Resizes image by the percent indicated by GameUI.BASE_SCALE_X and
	 * GameUI.BASE_SCALE_Y
	 * <p>
	 * 
	 * @param image
	 *            The image to be resized.
	 * @return The resized image.
	 */
	private BufferedImage resizeImage(BufferedImage image) {

		BufferedImage scaledImage;
		Graphics2D graphics2D;
		int width, height;

		width = (int) ((BASE_SCALE_X * image.getWidth()));
		height = (int) ((BASE_SCALE_Y * image.getHeight()));
		scaledImage = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_ARGB);
		graphics2D = scaledImage.createGraphics();
		graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		graphics2D.drawImage(image, 0, 0, width, height, null);
		graphics2D.dispose();

		return scaledImage;
	}

	public void mouseMoved(MouseEvent e) {
	}

	public void mouseDragged(MouseEvent e) {
	}

	public void actionPerformed(ActionEvent e) {
	}

	public void mouseClicked(MouseEvent e) {
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	public void mousePressed(MouseEvent e) {
		if (!gameInProgress) {
			returnToTitle(true);
		}
		if (e.getButton() == MouseEvent.BUTTON1) {
			mouseLeftPressed = true;
		} else {
			mouseRightPressed = true;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	public void mouseReleased(MouseEvent e) {
		if (e.getButton() != MouseEvent.BUTTON1) {
			if (mouseRightPressed) {
				mouseRightPressed = false;
				game.clearSelected();
				repaint();
				System.out.println("mouse released: \"other button\" paint");
			}
		} else {
			if (mouseLeftPressed) {
				mouseLeftPressed = false;
				int xPosition = e.getX();
				int yPosition = e.getY();
				Position coordinates = findPositionFromCoord(xPosition,
						yPosition);
				try {
					if (coordinates != null && gameInProgress) {
						if (game.select(coordinates.row, coordinates.col)) {
							update(this.getGraphics());
							System.out
									.println("mouse released: player 1 just selected something");
							System.out.println(game.toString());
						}
					}

					if (game.currentPlayer() && ai && gameInProgress) {
						int[] move = AI.getMove(game, game.difficulty);
						if (move != null) {
							System.out.printf("\nbest move %d %d, %d %d\n",
									move[0], move[1], move[2], move[3]);
							game.select(move[0], move[1]);
							update(this.getGraphics());
							Thread.sleep(PAUSE);
							game.select(move[2], move[3]);
							update(this.getGraphics());
							Thread.sleep(PAUSE);
							if (game.selectedEmpty() == false) {
								game.select(move[2], move[3]);
								update(this.getGraphics());
								Thread.sleep(PAUSE);
							}
						}
					}
				} catch (InterruptedException e1) {

				}
				System.out.println("gameboard AI score: " + game.scoreBoard());
			}
		}
		if (game.winner) {
			returnToTitle(false);
			p1Timer.stop();
			p2Timer.stop();
			p2Timer.setVisible(false);
			p1Timer.setVisible(false);
		} else if (game.currentPlayer() && gameInProgress) {
			p2Timer.start();
			p1Timer.stop();
			p1Timer.setVisible(false);
			p2Timer.setVisible(true);
		} else if (!game.currentPlayer() && gameInProgress) {
			p1Timer.start();
			p2Timer.stop();
			p2Timer.setVisible(false);
			p1Timer.setVisible(true);
		}
	}

	/**
	 * Ends the current game and sets the menu buttons to the title screen
	 * configuration. Repaints depending on the parameter repaint.
	 * 
	 * @param repaint
	 *            Whether or not to immediately repaint the screen.
	 */
	protected void returnToTitle(boolean repaint) {
		gameInProgress = false;
		p1Timer.setVisible(false);
		p2Timer.setVisible(false);
		menu.enableButtons(true, false, true, false);
		if (repaint) {
			repaint();
		}
	}

	/**
	 * Packages the GUI JPanel on a JFrame and displays it. Creates a SaveAPI
	 * instance and a menu bar. Adds closing capability for the "X" button.
	 */
	protected static void createAndShowGUI() {

		JFrame frame = new JFrame("Checkers");

		frame.setResizable(false);

		JComponent GameContentPane = new GameUI();
		GameContentPane.setOpaque(true);
		frame.setContentPane(GameContentPane);

		p1Timer = new Clock("Player 1");
		p2Timer = new Clock("Player 2");

		p1Timer.init();
		p2Timer.init();
		p1Timer.setVisible(false);
		p2Timer.setVisible(false);
		frame.add(p1Timer);
		p1Timer.setBounds(CLOCK_OFFSET, CLOCK_OFFSET, p1Timer.getWidth(),
				p1Timer.getHeight());
		p2Timer.setBounds(WIDTH - p2Timer.getWidth() - CLOCK_OFFSET,
				CLOCK_OFFSET, p2Timer.getWidth(), p2Timer.getHeight());
		frame.add(p2Timer);
		frame.pack();

		gameSaver = new SaveAPI();
		menu = new MenuManager((GameUI) GameContentPane, gameSaver);
		frameListener closer = menu.new frameListener();
		frame.addWindowListener(closer);
		frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

		JMenuBar menuBar = menu.createMenu();
		frame.setJMenuBar(menuBar);

		frame.setVisible(true);
	}

	/**
	 * Given a point represented by x and y, returns the position that contains
	 * the point or null if not found.
	 * <p>
	 * 
	 * @param x
	 *            X-coordinate of the given point.
	 * @param y
	 *            Y-coordinate of the given point.
	 * @return The position that contains the point, or null if not found.
	 */
	private Position findPositionFromCoord(int x, int y) {

		for (Position position : Position.values()) {
			if (pointInPolygon(position, x, y)) {
				return position;
			}
		}
		return null;
	}

	/**
	 * Finds a position based off of the row and column.
	 * 
	 * @param row
	 *            Row index of the desired position.
	 * @param col
	 *            Column index of the desired position.
	 * @return The Position specified by row and column if valid, otherwise
	 *         null.
	 */
	private Position findPositionFromIndex(int row, int col) {

		for (Position position : Position.values()) {
			if (row == position.row) {
				if (col == position.col) {
					return position;
				}
			}
		}
		return null;
	}

	/**
	 * Original algorithm written by W. Randolph Franklin returns true if the
	 * point (x,y) lies within the polygon.
	 * 
	 * Modified to be Java friendly and to work specifically with this project.
	 * 
	 * @param polygon
	 *            The area to test.
	 * @param x
	 *            X-coordinate of the point.
	 * @param y
	 *            Y-coordinate of the point.
	 * @return True if point is within the polygon.
	 */
	private boolean pointInPolygon(Position polygon, float x, float y) {

		int i = 0, j;

		Point[] points = new Point[SIDES];
		points[i++] = polygon.topLeft;
		points[i++] = polygon.topRight;
		points[i++] = polygon.bottomRight;
		points[i++] = polygon.bottomLeft;

		boolean c = false;
		for (i = 0, j = SIDES - 1; i < SIDES; j = i++) {
			if ((((points[i].y <= y) && (y < points[j].y)) || ((points[j].y <= y) && (y < points[i].y)))
					&& (x < (points[j].x - points[i].x) * (y - points[i].y)
							/ (points[j].y - points[i].y) + points[i].x)) {
				c = !c;
			}
		}
		return c;
	}
	
	/**
	 * toggles the pieces from one set to the other
	 */
	public void switchPieces(){
		if(pieceType == DEFAULT_PIECE){
			pieceType = PIRATE_PIECE;
			boardType = PIRATE_BOARD;
		}
		else{
			pieceType = DEFAULT_PIECE;
			boardType = DEFAULT_BOARD;
		}
		initializeImages();
		repaint();
	}

	/**
	 * Creates a new game. If there is a game being loaded, ignores ai and
	 * difficulty.
	 * 
	 * @param ai
	 *            Is the game against an AI.
	 * @param difficulty
	 *            The difficulty of the desired game if against AI.
	 * @param loadedGame
	 *            Game to be loaded.
	 */
	public void newGame(boolean ai, int difficulty, Game loadedGame) {
		if (loadedGame == null) {
			game = new Game(ai, difficulty);
		} else {
			game = loadedGame;
		}
		this.ai = ai;
		gameInProgress = true;
		menu.enableButtons(false, true, true, true);
		justLaunched = false;
		p1Timer.reset();
		p2Timer.reset();
		p1Timer.setVisible(true);
		p2Timer.setVisible(false);
		p1Timer.start();
		p2Timer.stop();
		repaint();
		startTime = System.currentTimeMillis();
	}

	/**
	 * Test for the GameUI class.
	 * 
	 */
	public static void test() {
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				GameUI.createAndShowGUI();
			}
		});
	}
}